package com.beantools.util;

import com.beantools.constants.SingleChar;
import com.beantools.util.valid.Validate;
import com.beantools.util.valid.ValidateFactory;

import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;

import static com.beantools.constants.MainASCCode.ASC_SPACE;
import static com.beantools.constants.MainASCCode.CHAR_SPACE;
import static com.beantools.constants.SingleChar.BLANK_OP;
import static com.beantools.constants.SingleChar.NULL_OP;
import static com.beantools.util.ObjectUtil.isNotNull;
import static com.sun.javafx.font.FontResource.ZERO;
import static java.math.BigDecimal.ROUND_UP;

/**
 * 操作字符串的工具类。在开发的过程中，时常会对字符串进行操作。
 *
 * @author baoya
 * @version 1.0
 * @since 1.1.0
 */
public class StringUtil implements java.io.Serializable, Comparable<Character> {

    @Override
    public int compareTo(Character o) {
        return 0;
    }

    private static int countContains = 0;

    /**
     * 判断字符串是否为null，但可以是空字符串
     *
     * @param cs 待验证的字符串
     * @return true|false 如果<code>cs</code>为空，则返回true，否则，返回false
     */
    public static boolean isEmpty(final CharSequence cs) {
        return cs == null || cs.length() == 0;
    }


    /**
     * 判断字符串不为null，或长度不为0
     *
     * @param cs 待验证的字符串
     * @param cs 待验证的字符串
     */
    public static boolean isNotEmpty(final CharSequence cs) {
        return !isEmpty(cs);
    }


    /**
     * 判断字符串是否为空字符串
     *
     * @param cs 待验证的字符串
     * @return 待验证的字符串是空数据，则返回true，否则返回false
     */
    public static boolean isBlank(final CharSequence cs) {
        if (isEmpty(cs)) {
            return true;
        }
        int length = cs.length();
        boolean whiteSpace = true;
        for (int i = 0; i < length; i++) {
            if (!isWhitespace(cs.charAt(i))) {
                whiteSpace = !whiteSpace;
                break;
            }
        }
        return whiteSpace;
    }

    /**
     * 判断字符串不是空字符串
     *
     * @param cs 待验证的字符串
     * @return 待验证的字符串不是空数据，则返回true，否则返回false
     */
    public static boolean isNotBlank(final CharSequence cs) {
        return !isBlank(cs);
    }


    /**
     * 判断字符串是否是空格
     *
     * @param codePoint 待校验的整型字符
     * @return 如果是空格，则返回true，否则，返回false
     */
    public static boolean isWhitespace(char codePoint) {
        return codePoint == ASC_SPACE;
    }

    /**
     * 判断字符串是否是空格
     *
     * @param codePoint 待校验的整型字符
     * @return 如果是空格，则返回true，否则，返回false
     */
    public static boolean isWhitespace(int codePoint) {
        return codePoint == ASC_SPACE;
    }


    /**
     * 判断可变字符串是否全部为空
     *
     * @param css 带校验的数组
     * @return 如果数组全部为空，则返回true，否则返回false
     */
    public static boolean isAllBlank(CharSequence... css) {
        if (ArrayUtil.isEmpty(css)) {
            return true;
        }
        for (CharSequence cs : css) {
            if (isNotBlank(cs)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断可变字符串是否包含空字符串
     *
     * @param css 带校验的数组
     * @return 如果数组包含空字符串，则返回true，否则返回false
     */
    public static boolean isAnyBlank(CharSequence... css) {
        if (ArrayUtil.isEmpty(css)) {
            return true;
        }
        for (CharSequence cs : css) {
            if (isBlank(cs)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 这里是用到去空操作，如果遇到空字符，就替换掉
     *
     * @param str 待去空的字符串
     */
    public static String trim(final String str) {
        return str == null ? null : str.replaceAll(BLANK_OP, NULL_OP);
    }

    /**
     * 去除空格之后是否为空。如果去除空字符串后是空，则返回null，否则返回去空后的对象
     *
     * @param str 待去空的字符串
     * @return 返回去空后的字符串
     */
    public static String trimToNull(final String str) {
        final String ts = trim(str);
        return isEmpty(ts) ? null : ts;
    }

    /**
     * 去除空格之后是否为空。如果去除空字符串后是空，则返回空格，否则返回去空后的对象
     *
     * @param str 待去空的字符串
     * @return 返回去空后的字符串
     */
    public static String trimToEmpty(final String str) {
        final String ts = trim(str);
        return isEmpty(ts) ? NULL_OP : ts;
    }


    /**
     * 获取有多少个目标字符串存在于源字符串中；如果没有，则返回0；
     * 否则，返回相应的次数
     *
     * @param srcLetter 源字符串
     * @param strToFind 查找字符串
     * @return 目标字符串在源字符串中存在的个数
     */
    public static int countContains(String srcLetter, String strToFind) {
        boolean contain = contains(srcLetter, strToFind);
        return contain ? countContains : 0;
    }

    /**
     * 程序在目标字符串中查找是否包含指定的字符串；如果查找到，则返回该字符串
     * 并标记有多少个该字符串
     *
     * @param strToFind 查找字符串
     * @param srcLetter 源字符串
     * @return true 表示包含该字符串，false表示不包含该字符串
     */
    public static boolean contains(String srcLetter, String strToFind) {
//        包含字符串
        countContains = 0;
        if (isAllBlank(srcLetter, strToFind)) {
            return false;
        }
        char[] srcChars = srcLetter.toCharArray();
        boolean equalsTrue = true;
        for (int i = 0; i < srcChars.length; i++) {
            char[] charToFinds = strToFind.toCharArray();
            if (srcChars[i] == charToFinds[0]) {
                for (int j = 0; j < charToFinds.length; j++) {
                    if (srcChars[i + j] != charToFinds[j]) {
                        equalsTrue = false;
                        break;
                    }
                }
                if (equalsTrue) {
                    ++countContains;
                }
            }
        }
        return countContains > 0 ? true : false;
    }

    /**
     * 去除指定的字符串
     *
     * @param removeChars 待去除的字符串
     * @return 返回去除的字符串
     */
    public static String remove(String src, String removeChars) {
        if (isBlank(src)) {
            return src;
        }
        int length = src.length();
        int var = removeChars.length();
        for (int i = 0; i < length; i++) {
            char var1 = src.charAt(i);
            for (int j = 0; j < var; j++) {
                char var2 = removeChars.charAt(i);
                if (var1 == var2) {
                    src.replace(var1, (char) CHAR_SPACE);
                }
            }
        }
        return src;
    }


    /**
     * 这是增加空格的方法。调用者在将非格式化的json转化为格式化的json时
     * 为了保证其美观度，调用者需要转化适当的流出空格。空格根据json层级
     * 来的。有多少层级，就留出多少空格。
     *
     * @param level json对象的层级
     * @return 返回的是空格长度
     */
    public static String appendSpace(int level) {
        if (level <= 0) {
            return SingleChar.DOUBLE_SPACE;
        }
        StringBuilder initSpace = new StringBuilder(SingleChar.DOUBLE_SPACE);
        for (int i = 0; i < level; i++) {
            initSpace.append(SingleChar.DOUBLE_SPACE);
        }
        return initSpace.toString();
    }

    /**
     * 通过正则表达式字符串中是否匹配某些规则
     *
     * @param character 待校验的字符串
     * @param regex     正则表达式
     * @return 验证成功，则返回true，验证失败，则返回false
     */
    public static boolean verifyRegex(String character, String regex) {
        return Pattern.compile(regex).matcher(character).matches();
    }

    /**
     * 判断参数是否在该数组中;如果待测参数和数组都为空，则返回false；
     * 如果待测参数至少有一个存在于数组中，则返回true
     *
     * @param param   校验字符串参数
     * @param filters 被测字符串数组
     * @return 参数存在于数组中，返回true；否则，返回false
     */
    public static boolean inAny(String param, String... filters) {
        if (isBlank(param)) {
            return false;
        }
        for (String tmp : filters) {
            if (tmp.equals(param)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断参数是否在该数组中;如果待测参数和数组都为空，则返回false；
     * 如果待测参数至少有一个存在于数组中，则返回true
     *
     * @param param   校验字符串参数
     * @param filters 被测字符串数组
     * @return 参数存在于数组中，返回true；否则，返回false
     */
    public static boolean containsAll(String param, String... filters) {
        if (isBlank(param)) {
            return false;
        }
        for (String tmp : filters) {
            if (!tmp.equals(param)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 这是替换字符串的方法。如果变长数组{@code strReplacing}在文本当中，那么就用
     * 替换字符{@code replacement}替换指定数组中的字符，返回被替换后的文本内容
     *
     * @param text          文本内容
     * @param replacement   替换字符
     * @param searchStrings 变长数组，存放着被替换的内容
     * @return 返回替换后的数据
     */
    public static String replaceAll(String text, String replacement, String... searchStrings) throws IllegalArgumentException {
        if (isEmpty(text)) {
            throw new IllegalArgumentException("文本不能为null");
        }
        if (null == replacement) {
            throw new IllegalArgumentException("替换字符不能为null");
        }
        if (ArrayUtil.isEmpty(searchStrings)) {
            throw new IllegalArgumentException("被替换的数组不能为null");
        }
        for (String searchString : searchStrings) {
            text = text.replace(searchString, replacement);
        }
        return text;
    }

    /**
     * 这是通过{@code obj}对象获取字符串类型对象
     *
     * @param obj 文本内容
     * @return 返回字符串类型或{@code null}值
     */
    public static String getString(Object obj) throws IllegalArgumentException {
        return (isNotNull(obj) && obj instanceof String) ? ((String) obj).replaceAll(BLANK_OP, NULL_OP) : null;
    }

    /**
     * 这是字符串后添加某个字段的方法。调用可以调用该方法，在指定的位置后面添加
     * 字符串，比如这段代码
     * <pre>
     *     <code>
     *          String sre = "99990101";
     *         System.out.println(StringUtil.insert(sre,"-",4));
     *     </code>
     * </pre>
     * 上面的结果是<strong>9999-0101</strong>
     *
     * @param srcSeq    原字符串，比如<strong>99990101</strong>
     * @param appendSeq 追加的字符串，比如<strong>-</strong>
     * @param locate    在原字符串的<code>srcSeq</code>哪个位置追加<code>appendSeq</code>
     */
    public static String insert(String srcSeq, String appendSeq, int locate) throws IndexOutOfBoundsException {
        if (isBlank(srcSeq) || isBlank(appendSeq)) {
            return srcSeq;
        }
        if (locate < ROUND_UP || locate > srcSeq.length()) {
            throw new IndexOutOfBoundsException("下标越界");
        }
        if (locate == srcSeq.length()) {
            return srcSeq + appendSeq;
        }
        if (locate == ZERO) {
            return appendSeq + srcSeq;
        }
        String before = srcSeq.substring(0, locate);
        String after = srcSeq.substring(locate);
        return before + appendSeq + after;
    }

    /**
     * 调用者如果在原字符串<code>srcJson</code>多个位置之后添加相同的字符串<code>appendSeq</code>
     * 那么，其可以调用这个方法。方法内部实现从小到大的排序和去重，比如以下的代码:
     * <pre>
     *     <code>
     *             int[] i1={7,3,7};
     *              System.out.println(StringUtil.insert(sre,"-",i1));
     *              int[] i2={4,9};
     *              System.out.println(StringUtil.insert(sre,"-",i2));
     *     </code>
     * </pre>
     *
     * @param srcSeq    原字符串
     * @param appendSeq 待添加的字符串
     * @param locates   指定位置的数组
     * @return 返回新的字符串，比如<code>stringBuilder.toString()</code>
     */
    public static String insert(String srcSeq, String appendSeq, int[] locates) {
        if (isBlank(srcSeq) || isBlank(appendSeq) || locates.length < 1) {
            return srcSeq;
        }
        List<Integer> integers = new LinkedList<>(SortUtil.removeRepetitionAfterSort(locates));
        boolean result = validSrcSeq(srcSeq, integers);
        if (!result) {
            throw new IndexOutOfBoundsException("下标越界");
        }
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i <= integers.size(); i++) {
            boolean isTrue = (i == 0 && integers.get(0) == 0);
            if (isTrue) {
                stringBuilder.append(appendSeq);
            } else if (i == integers.size() && integers.get(integers.size() - 1) == srcSeq.length()) {
                continue;
            } else if (i == 0 && integers.get(0) > 0) {
                stringBuilder.append(srcSeq.substring(0, integers.get(0)) + appendSeq);
            } else if (i == integers.size() && integers.get(integers.size() - 1) < srcSeq.length()) {
                stringBuilder.append(srcSeq.substring(integers.get(integers.size() - 1)));
            } else {
                stringBuilder.append(srcSeq.substring(integers.get(i - 1), integers.get(i)) + appendSeq);
            }
        }
        return stringBuilder.toString();
    }


    /**
     * 这是在源字符串<code>srcJson</code>中的某些字符串<code>afterCharSeq</code>后添加指定的
     * <code>appendSeq</code>，比如以下的代码：
     * <pre>
     *     <code>
     *          String srcJson="nbihaoahao";
     *          String appendSeq="--";
     *          String afterCharSeq="h";
     *          System.out.println(StringUtil.insert(srcJson,appendSeq,afterCharSeq));
     *     </code>
     * </pre>
     *
     * @param srcSeq       原字符串
     * @param appendSeq    待添加的字符串
     * @param afterCharSeq 在什么字符串之后添加
     * @return 返回更新后的源字符串
     */
    public static String insert(String srcSeq, String appendSeq, String afterCharSeq) {
        int var1 = countContains(srcSeq, afterCharSeq);
        if (var1 <= 0 || isBlank(appendSeq)) {
            return srcSeq;
        }
        String var2 = afterCharSeq + appendSeq;
        return srcSeq.replaceAll(afterCharSeq, var2);
    }

    /**
     * 当前字符串是否包含指定字符串，同时，不区分大小写
     *
     * @param srcLetter 源字符串
     * @param strToFind 待查找字符串
     * @return true|false 包含则返回true，不包含则返回false
     */
    public static boolean containsIgnoreCase(String srcLetter, String strToFind) {
        if (isAllBlank(srcLetter, strToFind)) {
            return false;
        }
        char[] srcChars = srcLetter.toCharArray();
        boolean equalsTrue = false;
        for (int i = 0; i < srcChars.length; i++) {
            char[] charToFinds = strToFind.toCharArray();
            int tem = getIgnoreCase(charToFinds[0]);
            if (srcChars[i] == charToFinds[0] || srcChars[i] == tem) {
                for (int j = 0; j < charToFinds.length; j++) {
                    tem = getIgnoreCase(charToFinds[j]);
                    if (srcChars[i + j] == charToFinds[j] || srcChars[i + j] == tem) {
                        if (j == charToFinds.length - 1) {
                            equalsTrue = true;
                            break;
                        }
                        continue;
                    }

                }
            }
        }
        return equalsTrue;
    }

    /**
     * 获取大写转化为小写的值，或者小写转为大写的值
     *
     * @param charToFind
     * @return
     */
    private static int getIgnoreCase(char charToFind) {
        return (charToFind >= 'a' && charToFind <= 'z') ?
                charToFind + 32 : ((charToFind >= 'A' && charToFind <= 'Z') ? charToFind - 32 : 0);
    }

    /**
     * Created By zby on 19:48 2019/5/16
     * 这是类<code>StringUtil</code>的私有方法，其主要来验证数组的每一个值是否大于<code>srcSeq</code>
     * 的长度，但凡有一个值大于<code>srcSeq</code>或小于0，则验证都不通过。
     *
     * @param srcSeq  源字符串
     * @param locates 数组
     * @return 如果验证成功，则返回true，否则，返回false
     */
    private static boolean validSrcSeq(String srcSeq, List<Integer> locates) {
        return Validate.validate(new ValidateFactory() {
            @Override
            public boolean validate(Object... objects) {
                for (Integer integer : locates) {
                    if (integer < ROUND_UP || integer > srcSeq.length()) {
                        return false;
                    }
                }
                return true;
            }
        }, srcSeq, locates);
    }

    /**
     * 将字符串的首字母转化为小写
     *
     * @param param 待转化为的字符串
     * @return 转化后的字符串
     */
    public static String firstLetterToLower(String param) {
        return letterToLower(param, 0, 1);
    }

    /**
     * 某些字母转小写
     *
     * @param param 字符串参数
     * @param start 开始位置
     * @param end   结束位置
     * @return 转化后的字符串
     */
    public static String letterToLower(String param, int start, int end) {
        if (ObjectUtil.isNull(param))
            throw new IllegalArgumentException("参数为空param=" + param);
        if (start < 0 || end < 0)
            throw new IllegalArgumentException("开始位置或结束位置不能为空");
        if (start >= end)
            throw new IllegalArgumentException("开始位置不能小于结束位置start=" + start + ",end=" + end);
        if (end > param.length())
            throw new IllegalArgumentException("结束位置越界，end=" + end);
        return param.substring(0, start) + param.substring(start, end).toLowerCase() + param.substring(end, param.length());
    }

}
